﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using Lucene.Net.Index;
using System.IO;
using System.Globalization;

namespace Search
{
    /// <summary>
    /// Enumerates the different states for Scheduler
    /// </summary>
    public enum SchedulerStatus
    {
        /// <summary>
        /// Scheduler is Alive and waiting for the next scheduled job
        /// </summary>
        Running,
        /// <summary>
        /// Scheduler is alive and a indexing job is running
        /// </summary>
        Indexing,
        /// <summary>
        /// Scheduler is not alive
        /// </summary>
        Stopped
    }

    /// <summary>
    /// A Factory class providing a basic operation for indexing site with a specific Culture.
    /// (Spider Scheduling, Run on demand, Stops spider etc...)
    /// It performs action according to the settings specified in the project settings
    /// </summary>
    public sealed class SpiderManager
    {
        /// <summary>
        /// Create a new instance of SpiderManger with the specified culture
        /// </summary>
        /// <param name="culture">The culture to assign to the SpiderManager</param>
        /// <returns>The instance</returns>
        public static SpiderManager Create(CultureInfo culture)
        {
            return new SpiderManager(culture);
        }

        #region Private Instance Fields
        private object locker;
        private bool indexing;
        private TimeSpan spiderFrequency;
        private DateTime startTime;
        private DateTime lastRun;
        private Spider.Spider spider;
        private Thread scheduler;
        private CultureInfo culture;
        private string indexFolder;
        #endregion

        #region Private Methods
        private SpiderManager(CultureInfo culture) 
        {
            locker = new object();
            indexing = false;
            spider = null;
            scheduler = null;
            this.culture = culture;
            indexFolder = SearchConfig.Instance.IndexFolder.EndsWith("\\") ?
                SearchConfig.Instance.IndexFolder + culture.TwoLetterISOLanguageName :
                SearchConfig.Instance.IndexFolder + "\\" + culture.TwoLetterISOLanguageName;
            lastRun = IndexReader.IndexExists(indexFolder) ?
                lastRun = new DateTime(IndexReader.LastModified(indexFolder)) :
                lastRun = DateTime.MinValue;
        }

        private bool IsIndexing()
        {
            lock (locker)
            {
                return indexing;
            }
        }
        private bool GetIndexing()
        {
            lock (locker)
            {
                if (indexing) return false;
                else
                {
                    indexing = true;
                    return true;
                }
            }
        }
        private void ReleaseIndexing()
        {
            lock (locker)
            {
                indexing = false;
            }
        }

        void SchedulerRun()
        {
            while (true)
            {
                TimeSpan toSleep = spiderFrequency;
                if (GetIndexing())
                {
                    TimeSpan tolerance = new TimeSpan(0, 15, 0); //15 minutes
                    TimeSpan remainTime = DateTime.Now - LastRun;
                    if (remainTime > spiderFrequency - tolerance)
                        try
                        {
                            Run();
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(string.Format("SpiderManager.Run\t{0}\t{1}", DateTime.Now.ToShortDateString(), e.Message));
                            Console.WriteLine(e.StackTrace);
                        }
                    else
                        toSleep = spiderFrequency - remainTime;
                    ReleaseIndexing();
                }
                Thread.Sleep(toSleep);
            }
        }

        void SetFolder()
        {
            if (!Directory.Exists(indexFolder))
            {
                Directory.CreateDirectory(indexFolder);
                if (!Directory.Exists(indexFolder))
                    throw new Exception("Cannot find or create Index folder: "+indexFolder);
            }
        }

        void Run()
        {
            startTime = DateTime.Now;

            //TODO 
            spider = new Spider.Spider(indexFolder, culture);
            //spider = new Spider.Spider(indexFolder);
            Uri home = new Uri(SearchConfig.Instance.SpiderUrl);
            spider.Start(home, 10);

            //TODO Redirect this output in a log file
            Console.WriteLine("Index created at " + indexFolder);
            Console.WriteLine("Indexing started at " + startTime);
            Console.WriteLine("Visited " + spider.UrlCount + " urls");
            Console.WriteLine("Indexing ended at " + DateTime.Now);
            spider = null;
            LastRun = DateTime.Now;
        }
        #endregion

        #region Properties
        public DateTime LastRun
        {
            get
            {
                Monitor.Enter(this);
                DateTime d = lastRun;
                Monitor.Exit(this);
                return d;
            }
            private set
            {
                Monitor.Enter(this);
                lastRun = value;
                Monitor.Exit(this);
            }
        }
        /// <summary>
        /// Get the scheduler thread status.
        /// </summary>
        public SchedulerStatus SchedulerStatus
        {
            get
            {
                if (scheduler != null && scheduler.IsAlive)
                {
                    if (IsIndexing()) return SchedulerStatus.Indexing;
                    else return SchedulerStatus.Running;
                }
                else return SchedulerStatus.Stopped;
            }
        }
        #endregion

        #region Public Methods

        /// <summary>
        /// Run the spider over the net. It uses the settings specified
        /// in the Project setting.
        /// </summary>
        /// <remarks>It can be take long time: consider to call on a different thread</remarks>
        /// <exception cref="System.Exception">If spider is already running</exception>
        public void RunSpider()
        {
            SetFolder();
            if (GetIndexing())
            {
                Run();
                ReleaseIndexing();
            }
            else throw new Exception("Spider is already running");
        }


        /// <summary>
        /// Try to stop the spider if it is already running, otherwise
        /// it has no effect
        /// </summary>
        public void StopSpider()
        {
            if (IsIndexing())
            {
                try { spider.Quit = true; }
                catch { }
            }
        }

        /// <summary>
        /// Run the scheduler thread for the spider. It performs a spider run and then set itself
        /// in to sleep mode, according to the settings specified in the project
        /// </summary>
        public void RunScheduler()
        {
            spiderFrequency = SearchConfig.Instance.SpiderFrequency;
            SetFolder();
            scheduler = new Thread(new ThreadStart(SchedulerRun));
            scheduler.Start();
        }

        /// <summary>
        /// This method simply aborts the scheduler thread.
        /// The operation may be raise Exception on calling System.Thread.Abort()
        /// </summary>
        public void StopScheduler()
        {
            if (scheduler != null && scheduler.IsAlive)
            {
                scheduler.Abort();
            }
        }
        #endregion
    }
}